package com.androidmtk;

import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.text.format.Time;

public class DownloadBinRunnable implements Runnable {

	private static final int SIZEOF_SECTOR = 0x10000;

	private final Handler dHandler;

	// Output date
	private final String file_time_stamp;

	// Preferences
	// Bluetooth device string
	private boolean create_log_file = false;
	private int SIZEOF_CHUNK = 0x1000;

	/** Nested class that performs the downloading of the log */
	File log_file = null;
	BufferedWriter log_writer = null;
	private boolean createGPX;

	private Handler cHandler;

	public DownloadBinRunnable(Handler downLoadDialogHandler,
			String file_time_stamp, Handler convertDialogHandler) {
		this.dHandler = downLoadDialogHandler;
		this.cHandler = convertDialogHandler;
		this.file_time_stamp = file_time_stamp;
		// this.file_time_stamp = "2012-07-29_1816";

		// Get some preferences information
		SIZEOF_CHUNK = Integer.parseInt(AndroidMTK.getSharedPreferences()
				.getString("chunkSizePref", "4096"));
		create_log_file = AndroidMTK.getSharedPreferences().getBoolean(
				"createDebugPref", false);

		createGPX = AndroidMTK.getSharedPreferences().getBoolean(
				"createGPXPref", true);

	}

	public int getFlashSize(int model) {
		// 8 Mbit = 1 Mb
		if (model == 0x1388)
			return (8 * 1024 * 1024 / 8); // 757/ZI v1
		if (model == 0x5202)
			return (8 * 1024 * 1024 / 8); // 757/ZI v2
		// 32 Mbit = 4 Mb
		if (model == 0x0000)
			return (32 * 1024 * 1024 / 8); // Holux M-1200E
		if (model == 0x0004)
			return (32 * 1024 * 1024 / 8); // 747 A+ GPS Trip Recorder
		if (model == 0x0005)
			return (32 * 1024 * 1024 / 8); // Qstarz BT-Q1000P
		if (model == 0x0006)
			return (32 * 1024 * 1024 / 8); // 747 A+ GPS Trip Recorder
		if (model == 0x000F)
			return (32 * 1024 * 1024 / 8); // 747 A+ GPS Trip Recorder
		if (model == 0x0008)
			return (32 * 1024 * 1024 / 8); // Pentagram PathFinder P 3106
		if (model == 0x8300)
			return (32 * 1024 * 1024 / 8); // Qstarz BT-1200
		// 16Mbit -> 2Mb
		// 0x0051 i-Blue 737, Qstarz 810, Polaris iBT-GPS, Holux M1000
		// 0x0002 Qstarz 815
		// 0x001b i-Blue 747
		// 0x001d BT-Q1000 / BGL-32
		// 0x0131 EB-85A
		return (16 * 1024 * 1024 / 8);
	}

	public void Log(String text) {
		if (log_writer != null) {
			// Create a unique file for writing the log files to
			Time now = new Time();
			now.setToNow();
			String time = now.format("%H:%M:%S ");
			try {
				log_writer.append(time);
				log_writer.append(text);
				log_writer.append('\n');
				log_writer.flush();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public void run() {
		String reply;
		Pattern p;
		Matcher m;
		BufferedOutputStream output_stream = null;
		sendPercentageConverted(0);
		sendMessageToMessageField("");
		// Open log file
		if (create_log_file) {
			log_file = new File(Environment.getExternalStorageDirectory(),
					"gpslog" + file_time_stamp + ".txt");
			try {
				log_writer = new BufferedWriter(new FileWriter(log_file));
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		Log(String.format("Trying to connect to GPS device: %s",
				AndroidMTK.getGPS_bluetooth_id()));
		GPSrxtx gpsdev = new GPSrxtx(AndroidMTK.getmBluetoothAdapter(),
				AndroidMTK.getGPS_bluetooth_id());
		if (gpsdev.connect()) {
			try {
				Log(String.format("Connected to GPS device: %s",
						AndroidMTK.getGPS_bluetooth_id()));

				// Query recording method when full (OVERLAP/STOP).
				Log("Sending command: PMTK182,2,6 and waiting for reply: PMTK182,3,6,");
				gpsdev.sendCommand("PMTK182,2,6");
				// Wait for reply from the device
				reply = gpsdev.waitForReply("PMTK182,3,6,");
				Log(String.format("Got reply: %s", reply));

				// log_full_method == 1 means overwrite from the beginning
				// log_full_method == 2 means stop recording
				int log_full_method = 0;
				p = Pattern.compile(".*PMTK182,3,6,([0-9]+).*");
				m = p.matcher(reply);
				if (m.find()) {
					log_full_method = Integer.parseInt(m.group(1));
				}
				Log(String.format("Recording method on memory full: %d",
						log_full_method));

				// Determine how much bytes we need to read from the memory
				int bytes_to_read = 0;
				if (log_full_method == 1) {
					// Device is in OVERLAP mode we don't know where data ends;
					// read
					// the entire memory.
					int flashManuProdID = 0;
					// Query memory information
					Log("Sending command: PMTK605 and waiting for reply: PMTK705,");
					gpsdev.sendCommand("PMTK605");
					// Wait for reply from the device
					reply = gpsdev.waitForReply("PMTK705,");
					Log(String.format("Got reply: %s", reply));
					p = Pattern
							.compile(".*PMTK705,[\\.0-9A-Za-z_-]+,([0-9A-Za-z]+).*");
					m = p.matcher(reply);
					if (m.find()) {
						flashManuProdID = Integer.parseInt(m.group(1), 16);
						Log(String.format("flashManuProdID: %d (0x%08X)",
								flashManuProdID, flashManuProdID));
					}
					bytes_to_read = getFlashSize(flashManuProdID);
				} else {
					int next_write_address = 0;
					// Query the RCD_ADDR (data log Next Write Address).
					Log("Sending command: PMTK182,2,8 and waiting for reply: PMTK182,3,8,");
					gpsdev.sendCommand("PMTK182,2,8");
					// Wait for reply from the device
					reply = gpsdev.waitForReply("PMTK182,3,8,");
					Log(String.format("Got reply: %s", reply));

					p = Pattern.compile(".*PMTK182,3,8,([0-9A-Za-z]+).*");
					m = p.matcher(reply);
					if (m.find()) {
						next_write_address = Integer.parseInt(m.group(1), 16);
						Log(String.format("Next write address: %d (0x%08X)",
								next_write_address, next_write_address));
					}
					int sectors = (int) Math.floor(next_write_address
							/ SIZEOF_SECTOR);
					if (next_write_address % SIZEOF_SECTOR != 0) {
						sectors += 1;
					}
					bytes_to_read = sectors * SIZEOF_SECTOR;
				}
				Log(String
						.format("Retrieving %d (0x%08X) bytes of log data from device...",
								bytes_to_read, bytes_to_read));

				// Open an output stream for writing
				File bin_file = new File(
						Environment.getExternalStorageDirectory(), "gpslog"
								+ file_time_stamp + ".bin");
				try {
					output_stream = new BufferedOutputStream(
							new FileOutputStream(bin_file), SIZEOF_SECTOR);
				} catch (FileNotFoundException e1) {
					e1.printStackTrace();
					return;
				}

				// To be safe we iterate requesting SIZEOF_CHUNK bytes at time.
				for (int offset = 0; offset < bytes_to_read; offset += SIZEOF_CHUNK) {
					// Request log data (PMTK_LOG_REQ_DATA) from offset to
					// bytes_to_read.
					String command = String.format("PMTK182,7,%08X,%08X",
							offset, SIZEOF_CHUNK);
					Log(String.format("Sending command: %s", command));

					if (!gpsdev.sendCommand(command)) {
						sendTOAST("Bluetooth connection problems");
						createGPX = false;
						break;
					}

					// Read from the device
					// It seems the chunk might be split over more than one
					// message
					// read until all bytes are received
					int number_of_empty = 0;
					byte[] tmp_array = new byte[SIZEOF_CHUNK];
					int bytes_received = 0;
					int number_of_message = 1;
					if (SIZEOF_CHUNK > 0x800) {
						number_of_message = SIZEOF_CHUNK / 0x800;
					}
					Log(String.format("Waiting for %d PMTK182,8 messages",
							number_of_message));
					for (int j = 0; j < number_of_message; j++) {
						reply = gpsdev.waitForReply("PMTK182,8");
						Log(String.format("Got reply: %s", reply));
						for (int i = 20; i < (reply.length() - 3); i += 2) {
							String string_byte = reply.substring(i, i + 2);
							if (string_byte.equals("FF")) {
								number_of_empty++;
							}
							try {
								tmp_array[bytes_received] = (byte) (Integer
										.parseInt(string_byte, 16) & 0xFF);
								bytes_received++;
							} catch (NumberFormatException e) {
								e.printStackTrace();

							}
						}
					}
					if (bytes_received != SIZEOF_CHUNK) {
						Log(String.format(
								"ERROR! bytes_received(%d) != SIZEOF_CHUNK",
								bytes_received));
						offset -= SIZEOF_CHUNK;
						continue;
					} else {
						try {
							output_stream.write(tmp_array, 0, SIZEOF_CHUNK);
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
					if (number_of_empty == bytes_received) {
						offset = bytes_to_read;
						Log(String
								.format("Found empty SIZEOF_CHUNK, stopping reading any further"));
					}

					double percentageComplete = ((offset + SIZEOF_CHUNK) / (double) bytes_to_read) * 100.0;
					Log(String.format("Saved log data: %6.2f%%",
							percentageComplete));
					sendPercentageConverted((int) percentageComplete);
				}
			} catch (BluetoothException be) {
				String exceptionMessage="Bluetooth connection problem: " + be.getMessage();
				sendTOAST(exceptionMessage);
				createGPX = false;
			}

			Log("Closing GPS device");
			gpsdev.close();

			// Close the bin file
			try {
				output_stream.flush();
				output_stream.close();
			} catch (IOException e) {
				e.printStackTrace();

			}
		} else {
			Log("Bluetooth connection problems");
			sendTOAST("Bluetooth connection problem. The GPS device might be inactive.");
			createGPX = false;
		}
		sendPercentageConverted(99);

		// Close the log file
		if (log_writer != null) {
			try {
				log_writer.close();
			} catch (IOException e) {
				e.printStackTrace();

			}
		}

		// Check if we should also create a GPX file
		if (createGPX) {

			ParseBinFile parseBinFile = new ParseBinFile(file_time_stamp,
					cHandler);

			try {
				parseBinFile.doConvert();
			} catch (IOException e) {
				sendTOAST("Couldn't create file for GPS log.");
				e.printStackTrace();
			}
		}

		// Send request to close the progress dialogs
		Message msg = dHandler.obtainMessage();
		Bundle b = new Bundle();
		b.putString(AndroidMTK.CLOSE_PROGRESS, "");
		msg.setData(b);
		dHandler.sendMessage(msg);
		msg = cHandler.obtainMessage();
		msg.setData(b);
		cHandler.sendMessage(msg);

	}

	private void sendTOAST(String message) {
		Message msg = dHandler.obtainMessage();
		Bundle b = new Bundle();
		b.putString(AndroidMTK.KEY_TOAST, message);
		msg.setData(b);
		dHandler.sendMessage(msg);
		sendMessageToMessageField(message);
	}

	private void sendPercentageConverted(int percentageComplete) {
		Message msg = dHandler.obtainMessage();
		Bundle b = new Bundle();
		b.putInt(AndroidMTK.KEY_PROGRESS, percentageComplete);
		msg.setData(b);
		dHandler.sendMessage(msg);
	}

	private void sendMessageToMessageField(String message) {

		Message msg = cHandler.obtainMessage();
		Bundle b = new Bundle();
		b.putString(AndroidMTK.MESSAGEFIELD, message);
		msg.setData(b);
		cHandler.sendMessage(msg);
	}
}
